<?
/**
 * Komfo Framework
 *
 * @category   Komfo
 * @package    Komfo_Framework
 * @subpackage Translate
 * @copyright  Copyright (c) 2008-2010 Komfo. (http://komfo.com)
 * @version    2.0
 */
 
/**
* KF_Translate
*
* @category   Komfo
* @package    Komfo_Framework
* @subpackage Translate
* @copyright  Copyright (c) 2008-2010 Komfo. (http://komfo.com)
* @version    2.0
* 
* Example Usage:
* <code>
* <?php
* $options = array(
* 	"locale" => "en_US", // default value
* 	"type" => "yaml", // the only option for now
* 	"auto_init" => true/false,
* 	"file_pattern" => APPLICATION_PATH . "/modules/%m/languages/%f.yaml",
* 	"session" => true/false,
* 	"session_name" => "KF_Translate", // this is the default
* 	"cache" => true/false,
* 	"cache_options" => array(
* 		"frontend" => array(
* 			"name" => "Core",
* 			"options" => array(
* 				"lifetime" => 7200,
* 				"automatic_serialization" => true
* 			)
* 		),
* 		"backend" => array(
* 			"name" => "File",
* 			"options" => array(
* 				"cache_dir" => APPLICATION_PATH . "/../cache/locale/"
* 			)
* 		),
* 	)
* );
* // All those are the defaults
* $translate = new KF_Translate($options);
* $translate->init($request->getParams());
* ?>
* </code> 
*/
class KF_Translate
{
	
	/**
     * @var array
     */
	protected $_options = array();
	
	/**
     * Constructor
     *
     * @param array $options
     */
	public function __construct(array $options)
	{
		$this->setOptions($options);
	}
	
	/**
     * Set options via an array
     *
     * @param array $options
     */
	public function setOptions(array $options)
	{
		$this->_options = $options;
	}
	
	/**
     * Get options array
     *
     * @return array The defined options
     */
	public function getOptions()
	{
		return $this->_options;
	}
	
	/**
     * Use the special method to get an option value
     *
     * @param  string Option name
     * @return mixed Option value
     * @throws KF_Exception When option does not exist
     */
	public function __get($option)
	{
        if(isset($this->_options[$option]))
        {
            return $this->_options[$option];
        }
        throw new KF_Exception("The option '{$option}' does not exist in KF_Translate");
	}
	
	/**
     * Get the locale long name
     *
     * @return string The locale long name - fx: en_US
     */
	public function getLocaleName()
	{
		return $this->locale;
	}
	
	/**
     * Get the locale short name
     *
     * @return string The locale short name - fx: en
     */
	public function getLocaleShortName()
	{
		return substr($this->locale, 0, 2);
	}
	
	/**
     * Initializes Locale
     *
     * @param  array $request_params
	 * @return void
     */
	public function init(array $request_params)
	{
		$options = $this->_options;	
		
		$file_pattern = isset($options["file_pattern"]) ? $options["file_pattern"] : APPLICATION_PATH . '/modules/%m/languages/%f.yaml';
		$file_pattern = str_replace('%m', $request_params["module"], $file_pattern);
		
		$this->calculateLocale($options, $request_params, $file_pattern);
		
		Zend_Registry::set('Zend_Locale', new Zend_Locale($options["locale"]));
		
		$locale_file_long = str_replace('%f', $options["locale"], $file_pattern);
		$locale_file_short = str_replace('%f', substr($options["locale"], 0, 2), $file_pattern);
        $locale_file = file_exists($locale_file_long) ? $locale_file_long : $locale_file_short;
		
		if($options["use_db"])
		{
            $translation = $this->getDbTranslation($options, $request_params, $locale_file_long);
		}
		else
		{
            if($options["cache"])
            {
                $cache = $this->getCache($options);
                $locale_cache_name = "{$request_params["module"]}_{$options["locale"]}_{$options["project_id"]}";
                
                if(!$translation = $cache->load($locale_cache_name))
                {
                    $translation = KF_Translate_Yaml::YAMLLoad($locale_file);
                    $cache->save($translation, $locale_cache_name);
                }
            }
            else
            {
                $translation = KF_Translate_Yaml::YAMLLoad($locale_file);
            }
		}
		
		Zend_Registry::set('KF_Locale', $translation);
	}
    
    /**
     * Validate locale
     *
     * @param string $locale
     * @return bool
     */
    public function validateLocale($locale = "")
    {
        return preg_match('/[a-z]{2}_[A-Z]{2}/', $locale) == 1;
    }
    
    /**
     * Get the translation from the database
     *
     * Principle of action: takes the last row for the project/module/locale
     * based on filemtime sorting. If it does not exist - gets the default
     * translation based on the file_pattern and saves it in the db. If it does
     * exist - check if the file found with the file_pattern is newer and if
     * yes - merges both and this is the result, if no - this is the result
     *
     * @param array $options
     * @param array $request_params
     * @param string $locale_file_long
     * @return array The translation
     */
    protected function getDbTranslation($options, $request_params, $locale_file_long)
    {
        // Get DB adapter
        $db_adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
        // Set default values
        $translation = array();
        $yaml = array();
        $yaml_text = '';
        $filemtime = file_exists($locale_file_long) ? filemtime($locale_file_long) : 0;
        if($this->validateLocale((string)$request_params['locale']) && $options['use_db_check_file'] == false)
        {
            $locale = $request_params['locale'];
        }
        else
        {
            $locale = $options['locale'];
        }
        
        // Do query to the DB for the yaml
        $sql = array(
            'query' => 'SELECT * FROM yaml WHERE module=? AND locale=? ' . ($options['project_id'] ? 'AND project_id=?' : '') . ' ORDER BY filemtime DESC, id DESC',
            'params' => array($request_params["module"], $locale),
        );
        if($options['project_id'])
        {
            $sql['params'][] = $options['project_id'];
        }
        $yaml_db_row = $db_adapter->fetchRow($sql['query'], $sql['params']);
        // If we have row - if it's old - create new using this as a base
        if($yaml_db_row)
        {
            $translation = unserialize($yaml_db_row['yaml']);
            if($yaml_db_row['filemtime'] < $filemtime)
            {
                $yaml = KF_Translate_Yaml::YAMLLoad($locale_file_long);
                $translation = $this->array_merge_recursive_distinct($yaml, $translation);
                // Save the new yaml
                $new_yaml_row = array(
                    'filemtime' => $filemtime,
                    'module' => $request_params["module"],
                    'locale' => $locale,
                    'yaml' => serialize($translation),
                );
                if($options['project_id'])
                {
                    $new_yaml_row['project_id'] = $options['project_id'];
                }
                $db_adapter->insert('yaml', $new_yaml_row);
            }
        }
        else if($filemtime > 0)
        {
            // get the parsed text
            $translation = KF_Translate_Yaml::YAMLLoad($locale_file_long);
            $new_yaml_row = array(
                'filemtime' => $filemtime,
                'module' => $request_params["module"],
                'locale' => $locale,
                'yaml' => serialize($translation),
            );
            if($options['project_id'])
            {
                $new_yaml_row['project_id'] = $options['project_id'];
            }
            $db_adapter->insert('yaml', $new_yaml_row);
        }
        
        return $translation;
    }
    
    /**
     * Calculate locale by request parameters and optionally store it in session
     *
     * @param array $options
     * @param array $request_params
     * @param string $file_pattern
     * @return void The result is stored in $options['locale']
     */
    protected function calculateLocale(&$options, $request_params, $file_pattern)
    {
        $session = null;
		if($options["session"])
		{
			$session_name = isset($options["session_name"]) ? $options["session_name"] : "KF_Translate";
			$session = new Zend_Session_Namespace($session_name);
			if(isset($session->locale) == false)
			{
				$session->locale = $options["locale"];
			}
			else
			{
				$options["locale"] = $session->locale;
			}
		}
		
		if(isset($request_params["locale"]) && $this->validateLocale($request_params["locale"]))
		{
			$test_file = str_replace('%f', $request_params["locale"], $file_pattern);
			$test_file_short = str_replace('%f', substr($request_params["locale"], 0, 2), $file_pattern);
			if(file_exists($test_file) || file_exists($test_file_short))
			{
				$options["locale"] = $request_params["locale"];
				if($session)
				{
					$session->locale = $request_params["locale"];
				}
			}
		}
    }
    
    /**
     * Get instance of Zend_Cache_Core for storing translation
     *
     * @param array $options Here are set the options for the cache
     * @return Zend_Cache_Core
     */
    protected function getCache($options)
    {
        // Frontend
        $frontendName = isset($options["cacheOptions"]["frontend"]["name"]) ? $options["cacheOptions"]["frontend"]["name"] : "Core";
        $frontendOptions = array(
           'lifetime' => 7200, // cache lifetime of 2 hours
           'automatic_serialization' => true
        );
        if(isset($options["cacheOptions"]["frontend"]["options"]) && is_array($options["cacheOptions"]["frontend"]["options"]))
        {
            $frontendOptions = array_merge($frontendOptions, $options["cacheOptions"]["frontend"]["options"]);
        }

        // Backend
        $backendName = isset($options["cacheOptions"]["backend"]["name"]) ? $options["cacheOptions"]["backend"]["name"] : "File";
        $backendOptions = array(
            'cache_dir' => APPLICATION_PATH . '/../data/cache/locale/' // Directory where to put the cache files
        );
        if(isset($options["cacheOptions"]["backend"]["options"]) && is_array($options["cacheOptions"]["backend"]["options"]))
        {
            $backendOptions = array_merge($backendOptions, $options["cacheOptions"]["backend"]["options"]);
        }

        // return a Zend_Cache_Core object
        return Zend_Cache::factory($frontendName, $backendName, $frontendOptions, $backendOptions);
    }

    /**
     * Custom function for merging arrays recursively
     *
     * In addition to the merge there is a bit of cutting - only existing
     * keys in $base are updated - e.g. new keys in $append are skipped
     *
     * @param array $base The base array to be updated
     * @param array $append The array with the new values
     * @return array
     */
    public function array_merge_recursive_distinct($base, $append)
    {
        if(!is_array($base))
        {
            $base = empty($base) ? array() : array($base);
        }
        if(!is_array($append))
        {
            $append = array($append);
        }
        foreach($append as $key => $value)
        {
            if(array_key_exists($key, $base))
            {
                if(is_array($value) or is_array($base[$key]))
                {
                    $base[$key] = $this->array_merge_recursive_distinct($base[$key], $value);
                }
                else
                {
                    $base[$key] = $value;
                }
            }
        }
        return $base;
    }
}